#!/usr/bin/env python2.5

import unittest

__all__ = ['Mock', 'MockMethod']

class Mock():
    def __init__(self, testcase):
        self.testcase = testcase
        self.expected_calls = []

    def __getattr__(self, name):
        if self.__dict__.__contains__(name):
            return self.__dict__[name]
        next_expected = None
        for expectation in self.expected_calls:
            if expectation.has_calls_remaining():
                next_expected = expectation
                break
        if next_expected is None:
            self.testcase.fail('Call to "%s" received when no calls expected!' % name)
        self.testcase.assertEqual(next_expected.name, name, 
                                  'Call to "%s" received when "%s" expected!' % (name, next_expected.name))
        return next_expected
            
    def expect(self, name, *args):
        expected = MockMethod(self.testcase, name)
        self.expected_calls.append(expected)
        return expected.expect(*args)
        
    def verify(self):
        for expected in self.expected_calls:
            expected.verify()

class MockMethod():
    def __init__(self, testcase, name):
        self.name = name
        self.testcase = testcase
        self.calls_remaining = 0
        self.expect_noargs()
        
    def __call__(self, *args):
        if not self.has_calls_remaining():
            self.testcase.fail('Unexpected call to %s received.' % self.name)
        self.calls_remaining -= 1
        self.testcase.assertEqual(self.expect_args, args, 'Unexpected args! %s' % '' if args is None else args)
        return self.return_from_call()
        
    def expect_noargs(self):
        return self.expect()
        
    def expect(self, *args):
        self.expect_args = args
        self.return_from_call = ReturnValue(self)
        return self.return_from_call.once()
    
    def has_calls_remaining(self):
        return self.calls_remaining > 0
    
    def verify(self):
        if self.has_calls_remaining():
            self.testcase.fail('Expected call to %s not received.' % self.name)

class ReturnValue():
    def __init__(self, mock_method):
        self.mock_method = mock_method
        self.returns(None)
        self.raises(None)
        
    def returns(self, value):
        self.value = value
        
    def raises(self, exception):
        self.exception = exception
        
    def times(self, number):
        self.mock_method.calls_remaining = number
        return self
        
    def once(self):
        return self.times(1)

    def twice(self):
        return self.times(2)
        
    def __call__(self):
        if self.exception:
            raise self.exception
        return self.value
        
        